all repos — caroster @ 9c60f22177be092c6ff394254db592cb09737543

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Tooltip from '@mui/material/Tooltip';
  3import IconButton from '@mui/material/IconButton';
  4import Box from '@mui/material/Box';
  5import Link from '@mui/material/Link';
  6import Card from '@mui/material/Card';
  7import Container from '@mui/material/Container';
  8import TextField from '@mui/material/TextField';
  9import Typography from '@mui/material/Typography';
 10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
 11import EventIcon from '@mui/icons-material/Event';
 12import TuneIcon from '@mui/icons-material/Tune';
 13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 14import {useTheme} from '@mui/material/styles';
 15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 16import {PropsWithChildren, useState} from 'react';
 17import {useTranslation} from 'react-i18next';
 18import pageUtils from '../../../lib/pageUtils';
 19import ShareEvent from '../../../containers/ShareEvent';
 20import PlaceInput from '../../../containers/PlaceInput';
 21import LangSelector from '../../../components/LangSelector';
 22import usePermissions from '../../../hooks/usePermissions';
 23import useEventStore from '../../../stores/useEventStore';
 24import useToastStore from '../../../stores/useToastStore';
 25import EventLayout, {TabComponent} from '../../../layouts/Event';
 26import {
 27  EventByUuidDocument,
 28  useUpdateEventMutation,
 29} from '../../../generated/graphql';
 30
 31interface Props {
 32  eventUUID: string;
 33  announcement?: string;
 34}
 35
 36const Page = (props: PropsWithChildren<Props>) => {
 37  return <EventLayout {...props} Tab={DetailsTab} />;
 38};
 39
 40const DetailsTab: TabComponent<Props> = ({}) => {
 41  const {t} = useTranslation();
 42  const {
 43    userPermissions: {canEditEventDetails},
 44  } = usePermissions();
 45  const theme = useTheme();
 46  const [updateEvent] = useUpdateEventMutation();
 47  const addToast = useToastStore(s => s.addToast);
 48  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 49  const event = useEventStore(s => s.event);
 50  const [isEditing, setIsEditing] = useState(false);
 51
 52  if (!event) return null;
 53
 54  const onSave = async e => {
 55    try {
 56      const {uuid, ...data} = event;
 57      const {
 58        id,
 59        travels,
 60        waitingPassengers,
 61        __typename,
 62        administrators,
 63        passengers,
 64        ...input
 65      } = data;
 66      await updateEvent({
 67        variables: {
 68          uuid,
 69          eventUpdate: {
 70            ...input,
 71          },
 72        },
 73        refetchQueries: ['eventByUUID'],
 74      });
 75      setIsEditing(false);
 76    } catch (error) {
 77      console.error(error);
 78      addToast(t('event.errors.cant_update'));
 79    }
 80  };
 81
 82  const modifyButton = isEditing ? (
 83    <Tooltip
 84      title={t('event.details.save')}
 85      sx={{
 86        position: 'absolute',
 87        top: theme.spacing(2),
 88        right: theme.spacing(2),
 89      }}
 90    >
 91      <IconButton color="primary" onClick={onSave}>
 92        <CheckCircleOutlineIcon />
 93      </IconButton>
 94    </Tooltip>
 95  ) : (
 96    <Tooltip
 97      title={t('event.details.modify')}
 98      sx={{
 99        position: 'absolute',
100        top: theme.spacing(2),
101        right: theme.spacing(2),
102      }}
103    >
104      <IconButton color="primary" onClick={() => setIsEditing(true)}>
105        <TuneIcon />
106      </IconButton>
107    </Tooltip>
108  );
109
110  return (
111    <Box
112      sx={{
113        position: 'relative',
114      }}
115    >
116      <Container
117        sx={{
118          p: 4,
119          mt: 6,
120          mb: 11,
121          mx: 0,
122          [theme.breakpoints.down('md')]: {
123            p: 2,
124          },
125        }}
126      >
127        <Card
128          sx={{
129            position: 'relative',
130            maxWidth: '100%',
131            width: '350px',
132            p: 2,
133          }}
134        >
135          <Typography variant="h4" pb={2}>
136            {t('event.details')}
137          </Typography>
138          {canEditEventDetails() && modifyButton}
139          <Box pt={2} pr={1.5}>
140            <Typography variant="overline">{t('event.fields.name')}</Typography>
141            <Typography>
142              {isEditing ? (
143                <TextField
144                  size="small"
145                  fullWidth
146                  value={event.name}
147                  onChange={e => setEventUpdate({name: e.target.value})}
148                  name="name"
149                  id="EditEventName"
150                />
151              ) : (
152                <Typography id="EventName">
153                  {event.name ?? t('event.fields.empty')}
154                </Typography>
155              )}
156            </Typography>
157          </Box>
158          <Box pt={2} pr={1.5}>
159            <Typography variant="overline">{t('event.fields.date')}</Typography>
160            {isEditing ? (
161              <Typography>
162                <DatePicker
163                  slotProps={{
164                    textField: {
165                      size: 'small',
166                      id: `EditEventDate`,
167                      fullWidth: true,
168                      placeholder: t('event.fields.date_placeholder'),
169                    },
170                  }}
171                  format="DD/MM/YYYY"
172                  value={moment(event.date)}
173                  onChange={date =>
174                    setEventUpdate({
175                      date: !date ? null : moment(date).format('YYYY-MM-DD'),
176                    })
177                  }
178                />
179              </Typography>
180            ) : (
181              <Box position="relative">
182                <Typography id="EventDate">
183                  {event.date
184                    ? moment(event.date).format('DD/MM/YYYY')
185                    : t('event.fields.empty')}
186                </Typography>
187                <EventIcon
188                  color="action"
189                  sx={{
190                    position: 'absolute',
191                    right: theme.spacing(-0.5),
192                    top: 0,
193                  }}
194                />
195              </Box>
196            )}
197          </Box>
198          <Box pt={2} pr={1.5}>
199            <Typography variant="overline">
200              {t('event.fields.address')}
201            </Typography>
202            {isEditing ? (
203              <PlaceInput
204                place={event.address}
205                latitude={event.latitude}
206                longitude={event.longitude}
207                onSelect={({place, latitude, longitude}) =>
208                  setEventUpdate({
209                    address: place,
210                    latitude,
211                    longitude,
212                  })
213                }
214              />
215            ) : (
216              <Box position="relative">
217                <Typography id="EventAddress" sx={{pr: 3}}>
218                  {event.address ? (
219                    <Link
220                      target="_blank"
221                      rel="noreferrer"
222                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
223                        event.address
224                      )}`}
225                      onClick={e => e.preventDefault}
226                    >
227                      {event.address}
228                    </Link>
229                  ) : (
230                    t('event.fields.empty')
231                  )}
232                </Typography>
233                <PlaceOutlinedIcon
234                  color="action"
235                  sx={{
236                    position: 'absolute',
237                    right: theme.spacing(-0.5),
238                    top: 0,
239                  }}
240                />
241              </Box>
242            )}
243          </Box>
244          <Box pt={2} pr={1.5}>
245            <Typography variant="overline">
246              {t('event.fields.description')}
247            </Typography>
248            {isEditing ? (
249              <Typography>
250                <TextField
251                  fullWidth
252                  multiline
253                  maxRows={4}
254                  inputProps={{maxLength: 250}}
255                  value={event.description || ''}
256                  onChange={e => setEventUpdate({description: e.target.value})}
257                  id={`EditEventDescription`}
258                  name="description"
259                />
260              </Typography>
261            ) : (
262              <Typography id="EventDescription" sx={{pr: 3}}>
263                {event.description ?? t('event.fields.empty')}
264              </Typography>
265            )}
266          </Box>
267          <Box pt={2} pr={1.5}>
268            <Typography variant="overline">{t('event.fields.lang')}</Typography>
269            {isEditing ? (
270              <LangSelector
271                value={event.lang}
272                onChange={lang => setEventUpdate({lang})}
273              />
274            ) : (
275              <Typography id="EventLang" sx={{pr: 3}}>
276                {event.lang
277                  ? t(`PROTECTED.languages.${event.lang}`)
278                  : t('event.fields.empty')}
279              </Typography>
280            )}
281          </Box>
282          {!isEditing && (
283            <ShareEvent
284              title={`Caroster ${event.name}`}
285              sx={{width: '100%', mt: 2}}
286            />
287          )}
288        </Card>
289      </Container>
290    </Box>
291  );
292};
293
294export const getServerSideProps = pageUtils.getServerSideProps(
295  async (context, apolloClient) => {
296    const {uuid} = context.query;
297    const {host = ''} = context.req.headers;
298    let event = null;
299
300    // Fetch event
301    try {
302      const {data} = await apolloClient.query({
303        query: EventByUuidDocument,
304        variables: {uuid},
305      });
306      event = data?.eventByUUID?.data;
307    } catch (error) {
308      return {
309        notFound: true,
310      };
311    }
312
313    return {
314      props: {
315        eventUUID: uuid,
316        metas: {
317          title: event?.attributes?.name || '',
318          url: `https://${host}${context.resolvedUrl}`,
319        },
320      },
321    };
322  }
323);
324export default Page;